/*
 * Copyright (c) 2019 Nuclei Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef __CORE_FEATURE_CLINT_H__
#define __CORE_FEATURE_CLINT_H__
/*!
 * @file     core_feature_clint.h
 * @brief    Core Local INTerrupt(Software and Timer Intr) header file
 */
/*
 * CLINT Feature Configuration Macro:
 * 1. __CLINT_PRESENT:  Define whether CLINT is present or not.
 *   * 0: Not present
 *   * 1: Present
 * 2. __CLINT_BASEADDR:  Define the base address of the CLINT.
 */
#ifdef __cplusplus
extern "C" {
#endif

#include "core_feature_base.h"

#if defined(__CLINT_PRESENT) && (__CLINT_PRESENT == 1)
/**
 * \defgroup NMSIS_Core_CLINT_Registers     Register Define and Type Definitions Of CLINT
 * \ingroup NMSIS_Core_Registers
 * \brief   Type definitions and defines for clint registers.
 *
 * @{
 */
/**
 * \brief  Structure type to access the CLINT.
 * \details
 * Structure definition to access the CLINT.
 */
typedef struct {
    __IOM uint32_t MSIP;                    /*!< Offset: 0x0000-0x0003 (R/W)  Hard 0 Machine Software Interrupt Pending  */
    __IOM uint32_t RESERVED0[4095];         /*!< Offset: 0x0004-0x3FFF Reserved */
    __IOM uint64_t MTIMERCMP;               /*!< Offset: 0x4000-0x4007 (R/W)  Machine Timer compare Value 64bits Register */
    __IOM uint64_t RESERVED1[4094];         /*!< Offset: 0x4008-0xBFF4 Reserved */
    __IOM uint64_t MTIMER;                  /*!< Offset: 0xBFF8-0xBFFC Machine Timer Register */
} CLINT_Type;

/* Timer Control / Status Register Definitions */
#define CLINT_MSIP_MSIP_Pos              0U                                          /*!< CLINT MSIP: MSIP bit Position */
#define CLINT_MSIP_MSIP_Msk              (1UL << CLINT_MSIP_MSIP_Pos)                /*!< CLINT MSIP: MSIP Mask */

#define CLINT_MTIMER_Msk                 (0xFFFFFFFFFFFFFFFFULL)                     /*!< CLINT MTIMER value Mask */
#define CLINT_MTIMERCMP_Msk              (0xFFFFFFFFFFFFFFFFULL)                     /*!< CLINT MTIMERCMP value Mask */

#define CLINT_MSIP_OFS                   (0x0000UL)                                  /*!< Software interrupt register offset  */
#define CLINT_MTIMERCMP_OFS              (0x4000UL)                                  /*!< MTIMERCMP register offset */
#define CLINT_MTIMER_OFS                 (0xBFF8UL)                                  /*!< MTIMER register offset*/


#ifndef __CLINT_BASEADDR
/* Base address of SYSTIMER(__SYSTIMER_BASEADDR) should be defined in <Device.h> */
#error "__SYSTIMER_BASEADDR is not defined, please check!"
#endif
/* CLINT Memory mapping of Device  */
#define CLINT_BASE                       __CLINT_BASEADDR                           /*!< CLINT Base Address */
#define CLINT                            ((CLINT_Type *) CLINT_BASE)                /*!< CLINT configuration struct */

/* CLINT register base */
#define CLINT_MSIP_BASE(hartid)        (unsigned long)((CLINT_BASE) + (CLINT_MSIP_OFS) + ((hartid) << 2))
#define CLINT_MTIMECMP_BASE(hartid)    (unsigned long)((CLINT_BASE) + (CLINT_MTIMERCMP_OFS) + ((hartid) << 3))
#define CLINT_MTIME_BASE               (unsigned long)((CLINT_BASE) + (CLINT_MTIMER_OFS))

/** @} */ /* end of group NMSIS_Core_CLINT_Registers */

/* ##################################    CLINT function  ############################################ */
/**
 * \defgroup NMSIS_Core_CLINT  Functions
 * \brief    Functions that configure the CLINT.
 * @{
 */
/**
 * \brief  Set machine timer load value
 * \details
 * This function set the machine timer load value in MTIMER register.
 * \param [in]  value   value to set machine MTIMER register.
 * \remarks
 * - Load value is 64bits wide.
 * - \ref CLINT_GetLoadValue
 */
__STATIC_FORCEINLINE void CLINT_SetLoadValue(uint64_t value)
{
#if __RISCV_XLEN == 32
    uint8_t *addr;
    addr = (uint8_t *)(&(CLINT->MTIMER));
    __SW(addr, 0);      // prevent carry
    __SW(addr + 4, (uint32_t)(value >> 32));
    __SW(addr, (uint32_t)(value));
#else
    CLINT->MTIMER = value;
#endif
}

/**
 * \brief  Get machine timer load value
 * \details
 * This function get the machine timer current value in MTIMER register.
 * \return  current value(64bit) of machine timer MTIMER register.
 * \remarks
 * - Load value is 64bits wide.
 * - \ref CLINT_SetLoadValue
 */
__STATIC_FORCEINLINE uint64_t CLINT_GetLoadValue(void)
{
#if __RISCV_XLEN == 32
    volatile uint32_t high0, low, high;
    uint64_t full;
    uint8_t *addr;

    addr = (uint8_t *)(&(CLINT->MTIMER));

    high0 = __LW(addr + 4);
    low = __LW(addr);
    high = __LW(addr + 4);
    if (high0 != high) {
        low = __LW(addr);
    }
    full = (((uint64_t)high) << 32) | low;
    return full;
#else
    return CLINT->MTIMER;
#endif
}

/**
 * \brief  Set machine timer compare value by hartid
 * \details
 * This function set the machine Timer compare value in MTIMERCMP register.
 * \param [in]  value   compare value to set machine timer MTIMERCMP register.
 * \param [in]  hartid  hart ID, one hart is required to have a known hart ID of 0, other harts ID can be in 1~1023.
 * \remarks
 * - Compare value is 64bits wide.
 * - If compare value is larger than current value timer interrupt generate.
 * - Modify the load value or compare value less to clear the interrupt.
 * - In S-mode, hartid can't be get by using __get_hart_id function, so this api suits S-mode particularly.
 * - \ref CLINT_GetHartCompareValue
 */
__STATIC_FORCEINLINE void CLINT_SetHartCompareValue(uint64_t value, unsigned long hartid)
{
    if (hartid == 0) {
#if __RISCV_XLEN == 32
        uint8_t *addr;
        addr = (uint8_t *)(&(CLINT->MTIMERCMP));
        __SW(addr, -1U);      // prevent load > timecmp
        __SW(addr + 4, (uint32_t)(value >> 32));
        __SW(addr, (uint32_t)(value));
#else
        CLINT->MTIMERCMP = value;
#endif
    } else {
        uint8_t *addr = (uint8_t *)(CLINT_MTIMECMP_BASE(hartid));
#if __RISCV_XLEN == 32
        __SW(addr, -1U);      // prevent load > timecmp
        __SW(addr + 4, (uint32_t)(value >> 32));
        __SW(addr, (uint32_t)value);
#else
        __SD(addr, value);
#endif
    }
}

/**
 * \brief  Set machine timer compare value in machined mode
 * \details
 * This function set the machine Timer compare value in MTIMERCMP register.
 * \param [in]  value   compare value to set system timer MTIMERCMP register.
 * \remarks
 * - Compare value is 64bits wide.
 * - If compare value is larger than current value timer interrupt generate.
 * - Modify the load value or compare value less to clear the interrupt.
 * - __get_hart_id function can only be accessed in machined mode, or else exception will occur.
 * - \ref CLINT_GetCompareValue
 */
__STATIC_FORCEINLINE void CLINT_SetCompareValue(uint64_t value)
{
    unsigned long hartid = __get_hart_id();
    CLINT_SetHartCompareValue(value, hartid);
}

/**
 * \brief  Get machine timer compare value by hartid
 * \details
 * This function get the machine timer compare value in MTIMERCMP register.
 * \param [in]  hartid  hart ID, one hart is required to have a known hart ID of 0, other harts ID can be in 1~1023.
 * \return  compare value of system timer MTIMERCMP register.
 * \remarks
 * - Compare value is 64bits wide.
 * - In S-mode, hartid can't be get by using __get_hart_id function, so this api suits S-mode particularly.
 * - \ref CLINT_SetHartCompareValue
 */
__STATIC_FORCEINLINE uint64_t CLINT_GetHartCompareValue(unsigned long hartid)
{
    if (hartid == 0) {
        return CLINT->MTIMERCMP;
    } else {
        uint64_t full;
        uint8_t *addr = (uint8_t *)(CLINT_MTIMECMP_BASE(hartid));
#if __RISCV_XLEN == 32
        // MTIMECMP didn't increase
        uint32_t high, low;

        high = __LW(addr + 4);
        low = __LW(addr);
        full = (((uint64_t)high) << 32) | low;
#else
        full = __LD(addr);
#endif
        return full;
    }
}

/**
 * \brief  Get machine timer compare value in machine mode
 * \details
 * This function get the machine timer compare value in MTIMERCMP register.
 * \return  compare value of machine timer MTIMERCMP register.
 * \remarks
 * - Compare value is 64bits wide.
 * - \ref CLINT_SetCompareValue
 */
__STATIC_FORCEINLINE uint64_t CLINT_GetCompareValue(void)
{
    unsigned long hartid = __get_hart_id();
    return CLINT_GetHartCompareValue(hartid);
}

/**
 * \brief  Trigger or set software interrupt via CLINT by hartid
 * \details
 * This function set the system timer MSIP bit in MSIP register.
 * \param [in]  hartid  hart ID, one hart is required to have a known hart ID of 0, other harts ID can be in 1~1023.
 * \remarks
 * - Set MSIP bit of MSIP register and generate a SW interrupt.
 * - \ref CLINT_ClearHartSWIRQ
 * - \ref CLINT_GetHartMsipValue
 */
__STATIC_FORCEINLINE void CLINT_SetHartSWIRQ(unsigned long hartid)
{
    if (hartid == 0) {
        CLINT->MSIP |= CLINT_MSIP_MSIP_Msk;
    } else {
        uint8_t *addr = (uint8_t *)(CLINT_MSIP_BASE(hartid));
        __SW(addr, CLINT_MSIP_MSIP_Msk);
    }
}

/**
 * \brief  Trigger or set software interrupt via CLINT in machine mode
 * \details
 * This function set the CLINT MSIP bit in MSIP register.
 * \remarks
 * - Set MSIP bit of MSIP register and generate a SW interrupt.
 * - \ref CLINT_ClearSWIRQ
 * - \ref CLINT_GetMsipValue
 */
__STATIC_FORCEINLINE void CLINT_SetSWIRQ(void)
{
    unsigned long hartid = __get_hart_id();
    CLINT_SetHartSWIRQ(hartid);
}

/**
 * \brief  Clear software interrupt pending request by hartid
 * \details
 * This function clear the MSIP bit in MSIP register.
 * \param [in]  hartid  hart ID, one hart is required to have a known hart ID of 0, other harts ID can be in 1~1023.
 * \remarks
 * - Clear MSIP bit in MSIP register to clear the software interrupt pending.
 * - In S-mode, hartid can't be get by using __get_hart_id function, so this api suits S-mode particularly.
 * - \ref CLINT_SetHartSWIRQ
 * - \ref CLINT_GetHartMsipValue
 */
__STATIC_FORCEINLINE void CLINT_ClearHartSWIRQ(unsigned long hartid)
{
    if (hartid == 0) {
        CLINT->MSIP &= ~CLINT_MSIP_MSIP_Msk;
    } else {
        uint8_t *addr = (uint8_t *)(CLINT_MSIP_BASE(hartid));
        __SW(addr, 0);
    }
}

/**
 * \brief  Clear software interrupt pending request in machine mode
 * \details
 * This function clear the system timer MSIP bit in MSIP register.
 * \remarks
 * - Clear MSIP bit in MSIP register to clear the software interrupt pending.
 * - \ref CLINT_SetSWIRQ
 * - \ref CLINT_GetMsipValue
 */
__STATIC_FORCEINLINE void CLINT_ClearSWIRQ(void)
{
    unsigned long hartid = __get_hart_id();
    CLINT_ClearHartSWIRQ(hartid);
}

/**
 * \brief  Get MSIP register value by hartid
 * \details
 * This function get the MSIP register value.
 * \param [in]  hartid  hart ID, one hart is required to have a known hart ID of 0, other harts ID can be in 1~1023.
 * \return    Value of MSIP register.
 * \remarks
 * - Bit0 is SW interrupt flag.
 *   Bit0 is 1 then SW interrupt set. Bit0 is 0 then SW interrupt clear.
 * - In S-mode, hartid can't be get by using __get_hart_id function, so this api suits S-mode particularly.
 * - \ref CLINT_SetHartSWIRQ
 * - \ref CLINT_ClearHartSWIRQ
 * - \ref CLINT_SetHartMsipValue
 */
__STATIC_FORCEINLINE uint32_t CLINT_GetHartMsipValue(unsigned long hartid)
{
    if (hartid == 0) {
        return (uint32_t)(CLINT->MSIP & CLINT_MSIP_MSIP_Msk);
    } else {
        uint8_t *addr = (uint8_t *)(CLINT_MSIP_BASE(hartid));
        return __LW(addr);
    }
}

/**
 * \brief  Get MSIP register value in machine mode
 * \details
 * This function get the MSIP register value.
 * \return    Value of MSIP register.
 * \remarks
 * - Bit0 is SW interrupt flag.
 *   Bit0 is 1 then SW interrupt set. Bit0 is 0 then SW interrupt clear.
 * - \ref CLINT_SetSWIRQ
 * - \ref CLINT_ClearSWIRQ
 * - \ref CLINT_SetMsipValue
 */
__STATIC_FORCEINLINE uint32_t CLINT_GetMsipValue(void)
{
    unsigned long hartid = __get_hart_id();
    return CLINT_GetHartMsipValue(hartid);
}

/**
 * \brief  Set MSIP register value by hartid
 * \details
 * This function set the MSIP register value.
 * \param [in]  msip   value to set MSIP register
 * \param [in]  hartid  hart ID, one hart is required to have a known hart ID of 0, other harts ID can be in 1~1023.
 * \remarks
 * - In S-mode, hartid can't be get using __get_hart_id function, so this api suits S-mode particularly.
 * - \ref CLINT_GetHartMsipValue
 */
__STATIC_FORCEINLINE void CLINT_SetHartMsipValue(uint32_t msip, unsigned long hartid)
{
    if (hartid == 0) {
        CLINT->MSIP = (msip & CLINT_MSIP_MSIP_Msk);
    } else {
        uint8_t *addr = (uint8_t *)(CLINT_MSIP_BASE(hartid));
        __SW(addr, msip);
    }
}

/**
 * \brief  Set MSIP register value in machine mode
 * \details
 * This function set the system timer MSIP register value.
 * \param [in]  msip   value to set MSIP register
 * - \ref CLINT_GetMsipValue
 */
__STATIC_FORCEINLINE void CLINT_SetMsipValue(uint32_t msip)
{
    unsigned long hartid = __get_hart_id();
    CLINT_SetHartMsipValue(msip, hartid);
}


/**
 * \brief  send ipi to target hart using CLINT
 * \details
 * This function send ipi using CLINT.
 * \param [in]  hart target hart
 */
__STATIC_FORCEINLINE void CLINT_SendIPI(unsigned long hartid)
{
    uint8_t *addr = (uint8_t *)(CLINT_MSIP_BASE(hartid));
    __SW(addr, 1);
}

/**
 * \brief  clear ipi to target hart using CLINT
 * \details
 * This function clear ipi using CLINT.
 * \param [in]  hart  target hart
 */
__STATIC_FORCEINLINE void CLINT_ClearIPI(unsigned long hartid)
{
    uint8_t *addr = (uint8_t *)(CLINT_MSIP_BASE(hartid));
    __SW(addr, 0);
}

/**
 * \brief   Machine Timer Tick Reload
 * \details Reload the Machine Timer Tick when the MTIMECMP reached TIME value
 *
 * \param [in]  ticks  Number of ticks between two interrupts.
 * \return          0  Function succeeded.
 * \return          1  Function failed.
 * \remarks
 * \sa
 * - \ref CLINT_SetCompareValue
 * - \ref CLINT_SetLoadValue
 */
__STATIC_FORCEINLINE uint32_t TimerTick_Reload(uint64_t ticks)
{
    uint64_t cur_ticks = CLINT_GetLoadValue();
    uint64_t reload_ticks = ticks + cur_ticks;

    if (__USUALLY(reload_ticks > cur_ticks)) {
        CLINT_SetCompareValue(reload_ticks);
    } else {
        /* When added the ticks value, then the MTIMERCMP < TIMER,
         * which means the MTIMERCMP is overflowed,
         * so we need to reset the counter to zero */
        CLINT_SetLoadValue(0);
        CLINT_SetCompareValue(ticks);
    }

    return (0UL);
}

/**
 * \brief   Machine Timer Tick Reload
 * \details Reload the Machine Timer Tick when the MTIMECMP reached TIME value
 *
 * \param [in]  ticks  Number of ticks between two interrupts.
 * \param [in]  hartid  hart ID, one hart is required to have a known hart ID of 0, other harts ID can be in 1~1023.
 * \return          0  Function succeeded.
 * \return          1  Function failed.
 * \remarks
 * \sa
 * - \ref CLINT_SetCompareValue
 * - \ref CLINT_SetLoadValue
 */
__STATIC_FORCEINLINE uint32_t TimerTick_HartReload(uint64_t ticks, unsigned long hartid)
{
    uint64_t cur_ticks = CLINT_GetLoadValue();
    uint64_t reload_ticks = ticks + cur_ticks;

    if (__USUALLY(reload_ticks > cur_ticks)) {
        CLINT_SetHartCompareValue(reload_ticks, hartid);
    } else {
        /* When added the ticks value, then the MTIMERCMP < TIMER,
         * which means the MTIMERCMP is overflowed,
         * so we need to reset the counter to zero */
        CLINT_SetLoadValue(0);
        CLINT_SetHartCompareValue(ticks, hartid);
    }

    return (0UL);
}

/** @} */ /* End of Doxygen Group NMSIS_Core_CLINT */

#endif /* defined(__CLINT_PRESENT) && (__CLINT_PRESENT == 1) */

#ifdef __cplusplus
}
#endif
#endif /* __CORE_FEATURE_CLINT_H__ */
